#ifndef SIMPLE_SUPPORT_TUPLE_UTILS_TRANSFORM_HPP
#define SIMPLE_SUPPORT_TUPLE_UTILS_TRANSFORM_HPP

#include <tuple> // std::tuple std::tuple_size_v
#include <algorithm> // std::min
#include <type_traits> // std::decay_t std::integral_constant std::enable_if_t std::is_invocable std::conditional_t std::is_lvalue_reference_v std::remove_reference_t std::remove_cvref_t
#include <utility> // std::make_index_sequence std::index_sequence std::forward std::declval

#include "../function_utils.hpp" // invoke
#include "../type_traits/has_tuple_interface.hpp" // has_tuple_interface_v
#include "common.hpp" // tuple_void adl_helper::get

namespace simple::support
{

	template <typename First, typename... Rest>
	constexpr size_t min_tuple_size()
	{
		size_t result = std::tuple_size_v<First>;
		if constexpr(sizeof...(Rest) != 0)
			result = std::min(result, min_tuple_size<Rest...>());
		return result;
	}

	template <typename... T>
	constexpr size_t min_tuple_size_v = min_tuple_size<T...>();

	template <typename... T>
	using min_tuple_index_sequence = std::make_index_sequence<min_tuple_size_v<std::decay_t<T>...>>;

	template <size_t Index, typename Range>
	// TODO: enable_if_t<Range is integer_sequence and Index is in Range>
	struct iteration_state
	{
		constexpr static std::integral_constant<decltype(Index), Index> index{};
		constexpr static Range range{};
	};
	template <size_t I, typename R>
	[[nodiscard]] constexpr bool
	operator==(const iteration_state<I,R>, const iteration_state<I,R>)
	noexcept { return true; }
	template <size_t I, typename R>
	[[nodiscard]] constexpr bool
	operator!=(const iteration_state<I,R>, const iteration_state<I,R>) noexcept
	{ return false; }

	template<typename F>
	constexpr decltype(auto) transform(F&& f)
	// TODO: noexcept(-_-)
	{
		return support::invoke(std::forward<F>(f));
	}

	template<size_t Index, typename F, typename... Tuples,
		std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr,
		std::enable_if_t<not std::is_invocable<F,
			decltype(adl_helper::get_<Index>(std::declval<Tuples>()))...>::value>* = nullptr,
		std::enable_if_t<std::is_invocable<F, iteration_state<Index, min_tuple_index_sequence<Tuples...>>,
			decltype(adl_helper::get_<Index>(std::declval<Tuples>()))...>::value>* = nullptr >
	constexpr
	decltype(auto) transform(F&& f, Tuples&&... tuples)
	// TODO: noexcept(-_-)
	{
		auto info = iteration_state<Index, min_tuple_index_sequence<Tuples...>>{};
		return support::invoke(std::forward<F>(f),
			info, adl_helper::get_<Index>(std::forward<Tuples>(tuples))... );
	}

	template<size_t Index, typename F, typename... Tuples,
		std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr,
		std::enable_if_t<std::is_invocable<F,
			decltype(adl_helper::get_<Index>(std::declval<Tuples>()))...>::value>* = nullptr >
	constexpr
	decltype(auto) transform(F&& f, Tuples&&... tuples)
	// TODO: noexcept(-_-)
	{
		return support::invoke(std::forward<F>(f),
			adl_helper::get_<Index>(std::forward<Tuples>(tuples))... );
	}

	namespace detail
	{

		template <typename T>
		using carry_value_t = std::conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cvref_t<T>>;

	} // namespace detail

	template<typename F, typename... Tuples, size_t... Indecies,
		std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr>
	constexpr
	decltype(auto) transform(F&& f, std::index_sequence<Indecies...>, Tuples&&... tuples)
	// TODO: noexcept(-_-)
	{
		// TODO: pass the index sequence through for use in iteration_state
		return std::tuple
		<
			detail::carry_value_t<decltype((transform<Indecies>(std::forward<F>(f), std::forward<Tuples>(tuples)...),
				tuple_void) )>
			...
		>
		// return std::forward_as_tuple
		{
			(transform<Indecies>(std::forward<F>(f), std::forward<Tuples>(tuples)...),
				tuple_void )
			...
		};
	}

	template<typename F, typename... Tuples,
		std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr>
	constexpr
	decltype(auto) transform(F&& f, Tuples&&... tuples)
	// TODO: noexcept(-_-)
	{
		return transform(std::forward<F>(f),
			min_tuple_index_sequence<Tuples...>{} ,
			std::forward<Tuples>(tuples)...);
	}

	// TODO: when c++20 use std::make_from_tuple instead
	// https://stackoverflow.com/questions/70020593/stdmake-from-tuple-doesnt-compile-without-constructor
	// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0960r3.html
	namespace detail
	{
		template <typename T, typename Tuple, std::size_t... I>
		constexpr T from_tuple_impl( Tuple&& t, std::index_sequence<I...> )
		{
			return T{adl_helper::get_<I>(std::forward<Tuple>(t))...};
		}
	} // namespace detail
	template <typename T, typename Tuple>
	constexpr T from_tuple( Tuple&& t )
	{
		return detail::from_tuple_impl<T>(std::forward<Tuple>(t),
			std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
	}

} // namespace simple::support

#endif /* end of include guard */
